package com.hongji.lms5xxcommunicator.scanner.imp;

import com.hongji.lms5xxcommunicator.common.LmdHeader;
import com.hongji.lms5xxcommunicator.realtime.sync.XSource;
import com.hongji.lms5xxcommunicator.scanner.ScanFrame;
import com.hongji.lms5xxcommunicator.scanner.ScanStrategy;
import com.hongji.lms5xxcommunicator.scannerSettingWindow.LmdMeta;
import com.hongji.lms5xxcommunicator.scannerSettingWindow.LmdMetaParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
@Service
public class Lms5xxScannerStrategy implements ScanStrategy {

    @Value("${scanner.LMS511.device_ip}") private String deviceIp;
    @Value("${scanner.LMS511.port:2111}") private int port;
    @Value("${scanner.LMS511.timeout:2000}") private int timeoutMs;
    @Value("${scanner.LMS511.height_size}") private int heightSize;

    // —— 窗口过滤阈值（以 PLC 坐标为准）——
    @Value("${scanner.LMS511.filter.z_min}") private double zMin;
    @Value("${scanner.LMS511.filter.z_max}") private double zMax;
    @Value("${grid.spec.y0}") private double yMin;
    @Value("${grid.spec.y1}") private double yMax;

    // —— 坐标统一（传感器→PLC）：Y/Z 在策略内统一 —— //
    @Value("${scanner.coord.enabled:true}")      private boolean coordEnabled;
    @Value("${scanner.coord.y_base}")            private double yBase;
    @Value("${scanner.coord.z_offset}")          private double zOffset;
    @Value("${scanner.coord.y_scale:1.0}")       private double yScale;
    @Value("${scanner.coord.z_scale:1.0}")       private double zScale;
    @Value("${scanner.coord.invert_y:false}")    private boolean invertY;
    @Value("${scanner.coord.invert_z:false}")    private boolean invertZ;

    // —— X 最近邻容差（ms）——
    @Value("${x.lookup.tolerance.ms:30}") private int xLookupTolMs;

    // —— 配置兜底；优先解析设备报文 —— //
    @Value("${scanner.LMS511.step_angle}")    private double cfgStepAngle;
    @Value("${scanner.LMS511.start_angle}")   private double cfgStartAngle;
    @Value("${scanner.LMS511.end_angle}")     private double cfgEndAngle;
    @Value("${scanner.LMS511.login_command}") private String loginCommand;
    @Value("${scanner.LMS511.scan_count:0}")  private int configuredScanCount;

    // 设备时间提取位置
    @Value("${scanner.LMS511.idx_time_startup:9}") private int idxTimeSinceStartup;
    @Value("${scanner.LMS511.idx_time_tx:10}")     private int idxTimeOfTransmission;

    // 注入 XSource（内部已做 OPC 采样 + 坐标统一）
    @Autowired private XSource xSource;

    private volatile ScanFrameListener listener;
    private final AtomicBoolean running = new AtomicBoolean(false);
    private ExecutorService ioExec;
    private Socket sock; private InputStream in; private OutputStream out;

    // 本策略内的“上一次有效 X”，用于 nearest 的回退
    private volatile double lastX = 0.0;

    @Override
    public void start(ScanFrameListener listener) throws Exception {
        if (!running.compareAndSet(false, true)) return;
        this.listener = listener;

        open();
        setup();

        // 开启 XSource 采样
        xSource.start();

        ioExec = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "lms5xx-io");
            t.setDaemon(true);
            return t;
        });
        ioExec.submit(this::readLoop);
        log.info("[LMS5xx] started (coordEnabled={}, yBase={}, zOffset={}, yScale={}, zScale={}, invertY={}, invertZ={})",
                coordEnabled, yBase, zOffset, yScale, zScale, invertY, invertZ);
    }

    @Override
    public void stop() {
        if (!running.getAndSet(false)) return;
        try { send("sEN LMDscandata 0"); readOne(); } catch (Exception ignore) {}
        try { send("sMN LMCstopmeas");    readOne(); } catch (Exception ignore) {}
        closeResources();
        if (ioExec != null) ioExec.shutdownNow();

        // 关闭 XSource 采样
        try { xSource.stop(); } catch (Exception ignore) {}

        log.info("[LMS5xx] stopped");
    }

    @Override
    public boolean isRunning() { return running.get(); }

    private void open() throws IOException {
        sock = new Socket();
        sock.connect(new InetSocketAddress(deviceIp, port), Math.max(1000, timeoutMs));
        sock.setTcpNoDelay(true);
        sock.setSoTimeout(0);
        in  = new BufferedInputStream(sock.getInputStream());
        out = new BufferedOutputStream(sock.getOutputStream());
    }

    private void setup() throws IOException {
        send(loginCommand);         readOne();
        send("sEN LMDscandata 0");  readOne();
        send("sMN Run");            readOne();
        send("sMN LMCstartmeas");   readOne();
        send("sEN LMDscandata 1");  readOne();
    }

    private void send(String cmd) throws IOException {
        out.write(0x02);
        out.write(cmd.getBytes(StandardCharsets.US_ASCII));
        out.write(0x03);
        out.flush();
    }

    private String readOne() {
        try {
            sock.setSoTimeout(1500);
            final int STX = 0x02, ETX = 0x03;
            boolean inF = false;
            ByteArrayOutputStream b = new ByteArrayOutputStream(256);
            while (true) {
                int c = in.read();
                if (c == -1) return null;
                if (!inF) {
                    if (c == STX) { inF = true; b.reset(); }
                } else {
                    if (c == ETX) break; else b.write(c);
                }
            }
            return new String(b.toByteArray(), StandardCharsets.US_ASCII);
        } catch (Exception e) {
            return null;
        } finally {
            try { sock.setSoTimeout(0); } catch (Exception ignore) {}
        }
    }

    /** 仅内部资源释放 */
    private void closeResources() {
        try { if (in  != null) in.close();  } catch (Exception ignore) {}
        try { if (out != null) out.close(); } catch (Exception ignore) {}
        try { if (sock!= null) sock.close();} catch (Exception ignore) {}
    }

    private void readLoop() {
        final int STX = 0x02, ETX = 0x03;
        boolean inF = false;
        ByteArrayOutputStream buf = new ByteArrayOutputStream(64 * 1024);
        try {
            while (running.get()) {
                int b = in.read();
                if (b == -1) break;
                if (!inF) {
                    if (b == STX) { inF = true; buf.reset(); }
                } else {
                    if (b == ETX) {
                        long hostNs = System.nanoTime();
                        byte[] frameBytes = buf.toByteArray();
                        String ascii = new String(frameBytes, StandardCharsets.US_ASCII);

                        if (ascii.contains("LMDscandata")) {
                            Long devUs = extractDeviceUs(ascii);
                            YZ yz = parseUsingDeviceMeta(frameBytes, frameBytes.length, ascii);
                            if (yz.n > 0 && listener != null) {
                                // —— 在策略内使用 XSource 做最近邻 —— //
                                final long tolNs = Math.max(1, xLookupTolMs) * 1_000_000L;
                                double xPoint = xSource.nearestAtHostNs(hostNs, tolNs, lastX);
                                lastX = xPoint;

                                double[] xs = new double[yz.n];
                                Arrays.fill(xs, xPoint);

                                double[] ys = new double[yz.n];
                                double[] zs = new double[yz.n];
                                System.arraycopy(yz.y, 0, ys, 0, yz.n);
                                System.arraycopy(yz.z, 0, zs, 0, yz.n);
                                listener.onFrame(new ScanFrame(hostNs, devUs, xs, ys, zs));
                            }
                        }
                        inF = false; buf.reset();
                    } else buf.write(b);
                }
            }
        } catch (Exception e) {
            if (running.get()) log.warn("[LMS5xx] reader stopped: {}", e.toString());
        }
    }

    private Long extractDeviceUs(String ascii) {
        long us = LmdHeader.extractDeviceTimeUs(ascii, idxTimeSinceStartup, idxTimeOfTransmission);
        return us >= 0 ? us : null;
    }

    // 内部结构体
    private static final class YZ {
        final double[] y, z; final int n;
        YZ(double[] y, double[] z, int n) { this.y = y; this.z = z; this.n = n; }
    }

    // —— 解析（优先设备元信息） —— //
    private YZ parseUsingDeviceMeta(final byte[] asciiBuf, final int len, final String ascii) {
        LmdMeta meta = LmdMetaParser.parse(asciiBuf, len);

        if (meta != null) {
            String s = new String(asciiBuf, 0, len, StandardCharsets.US_ASCII).trim();
            String[] tk = s.split("\\s+");
            int n = meta.count;
            if (configuredScanCount > 0 && configuredScanCount < n) n = configuredScanCount;

            // 第一遍计数（含补偿与过滤）
            int valid = 0;
            for (int i = 0, ti = meta.distanceStartIdx; i < n; i++, ti++) {
                int raw = parseHexUnsigned(tk[ti]);
                double dist = Math.round(raw * meta.scale + meta.offset); // mm
                double deg  = meta.startAngleDeg + i * meta.stepAngleDeg;
                double rad  = Math.toRadians(deg);
                double y = dist * Math.cos(rad);
                double z = heightSize - dist * Math.sin(rad);
                double[] yz = transformYZToPlc(y, z);
                if (isInWindow(yz[0], yz[1])) valid++;
            }

            double[] ys = new double[valid];
            double[] zs = new double[valid];
            int p = 0;
            for (int i = 0, ti = meta.distanceStartIdx; i < n; i++, ti++) {
                int raw = parseHexUnsigned(tk[ti]);
                double dist = Math.round(raw * meta.scale + meta.offset);
                double deg  = meta.startAngleDeg + i * meta.stepAngleDeg;
                double rad  = Math.toRadians(deg);
                double y = dist * Math.cos(rad);
                double z = heightSize - dist * Math.sin(rad);
                double[] yz = transformYZToPlc(y, z);
                if (isInWindow(yz[0], yz[1])) { ys[p]=yz[0]; zs[p]=yz[1]; p++; }
            }
            return new YZ(ys, zs, valid);
        }

        return fallbackParseByConfig(ascii);
    }

    // —— 配置回退解析 —— //
    private YZ fallbackParseByConfig(final String raw) {
        if (raw == null || raw.isEmpty()) return new YZ(new double[0], new double[0], 0);
        int s = 0, e = raw.length();
        if (raw.charAt(0) == 0x02) s++;
        if (e > s && raw.charAt(e - 1) == 0x03) e--;

        final String line = raw.substring(s, e).trim();
        final String[] tk = line.split("\\s+");

        int distIdx = -1;
        for (int i = 0; i < tk.length; i++) {
            if ("DIST1".equalsIgnoreCase(tk[i])) { distIdx = i; break; }
        }
        if (distIdx < 0 || distIdx + 6 >= tk.length) return new YZ(new double[0], new double[0], 0);

        int nAll;
        try { nAll = parseHexUnsigned(tk[distIdx + 5]); } catch (Throwable ignore) { nAll = 0; }
        if (nAll <= 0) return new YZ(new double[0], new double[0], 0);

        int distStart = distIdx + 6;
        int scanCount = getEffectiveScanCountByConfig();
        int n = Math.min(nAll, scanCount);

        int valid = 0;
        double deg = cfgStartAngle;
        for (int i = 0; i < n; i++) {
            int rawDist = parseHexUnsigned(tk[distStart + i]);
            double dist = rawDist; // 回退模式：不应用 scale/offset
            double rad = Math.toRadians(deg);
            double y = dist * Math.cos(rad);
            double z = heightSize - dist * Math.sin(rad);
            double[] yz = transformYZToPlc(y, z);
            if (isInWindow(yz[0], yz[1])) valid++;
            deg += cfgStepAngle;
        }

        double[] ys = new double[valid];
        double[] zs = new double[valid];
        int p = 0;
        deg = cfgStartAngle;
        for (int i = 0; i < n; i++) {
            int rawDist = parseHexUnsigned(tk[distStart + i]);
            double dist = rawDist;
            double rad = Math.toRadians(deg);
            double y = dist * Math.cos(rad);
            double z = heightSize - dist * Math.sin(rad);
            double[] yz = transformYZToPlc(y, z);
            if (isInWindow(yz[0], yz[1])) { ys[p]=yz[0]; zs[p]=yz[1]; p++; }
            deg += cfgStepAngle;
        }
        return new YZ(ys, zs, valid);
    }

    // —— 工具 —— //
    private boolean isInWindow(double y, double z) {
        return y>= yMin && y<= yMax && z >= zMin && z <= zMax;
    }

    /** 传感器 Y/Z → PLC 坐标（先翻转→缩放→平移） */
    private double[] transformYZToPlc(double y, double z) {
        if (!coordEnabled) return new double[]{y, z};
        if (invertY) y = -y;
        if (invertZ) z = -z;
        y = y * yScale;
        z = z * zScale;
        y = y + yBase;
        z = z + zOffset;
        return new double[]{y, z};
    }

    /** 无符号 32-bit 十六进制 -> int */
    private static int parseHexUnsigned(String hex) {
        String h = (hex.startsWith("0x") || hex.startsWith("0X")) ? hex.substring(2) : hex;
        long v = Long.parseUnsignedLong(h, 16);
        return (int) (v & 0xFFFFFFFFL);
    }

    private int getEffectiveScanCountByConfig() {
        if (configuredScanCount > 0) return configuredScanCount;
        if (cfgStepAngle == 0.0d) return 1;
        double raw = (cfgEndAngle - cfgStartAngle) / cfgStepAngle;
        return Math.max(1, (int) Math.ceil(raw) + 1);
    }
}
